package game

import (
	"archive/zip"
	"bytes"
	"errors"
	"fmt"
	"reflect"
	"sort"
	"sync"
	"time"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/binocle"
	. "gddgame.cc/galaxy/common/dsl/db"
	. "gddgame.cc/galaxy/common/dsl/web"
	"gddgame.cc/galaxy/common/namespaces"
	"gddgame.cc/galaxy/core/models"
	"gddgame.cc/galaxy/satellite/formula/nebula"
	"gddgame.cc/galaxy/server/web"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/cache"
	"gddgame.cc/galaxy/utils/def"
	"gddgame.cc/galaxy/utils/orm"
	"gddgame.cc/galaxy/utils/serialize"
	"gddgame.cc/galaxy/utils/serialize/msgpack"
)

const (
	AUTH nebula.NS = iota
	SCENE
	ROOM
	TEAM
	COOPERATE
	AREA = 10
	INFO = 127
)

const (
	RoomKey     = "room"
	TeamKey     = "team"
	LanguageKey = "language"
	ZoneKey     = "zone"
)

const (
	LinkTable = "db_link"
)

var (
	DefaultLanguageList = []string{"zh", "en", "jp", "ko", "pt", "fr", "es", "de", "be", "ar"}
)

const DefaultLanguage = "zh"

type EnvHandle func(env *Environment) error
type AppHandle func(app binocle.App, env *Environment) error
type DataHandle func(language string) (interface{}, error)

type Platform interface {
	GetOpen(openId string) (*models.UserOpen, error)
	GetOpenList(openIds []string) ([]models.UserOpen, error)
	GetFriendList(openId string, status bool, updateTime *time.Time) ([]models.UserOpen, error)
	IsFriend(openId string, friendOpenList []models.UserOpen) []bool
	SearchUserList(keyword string, page int, number int) ([]models.UserOpen, error)
	GetOpenStore(openId string, key string) (interface{}, error)
	UpdateOpenStore(openId string, key string, value interface{}) error
}
type Environment struct {
	id       string
	context  def.Context
	app      binocle.App
	config   def.Store
	lastTime time.Time

	Platform
	LanguageList []string

	def.ExpireStore
	*cache.Wrap

	namespaces.AuthManager
	namespaces.SceneManager
	namespaces.RoomManager
	namespaces.TeamManager
	namespaces.CooperateManager

	onInitHandles      []EnvHandle
	onNamespaceHandles []EnvHandle
	modules            []ModuleProvider
	moduleNameMap      map[string]*ModuleProvider

	namespace bool
	reload    bool
	datas     map[string]*DataProvider

	models sync.Map
}

func NewEnvironment(context def.Context, id string, platform Platform, store def.ExpireStore) *Environment {
	c := cache.Memory(context)

	env := &Environment{
		id:      id,
		context: context,
		config:  c.Store(id),

		Platform:     platform,
		LanguageList: DefaultLanguageList,

		ExpireStore: store,
		Wrap:        cache.NewWrap(store),

		onInitHandles:      []EnvHandle{},
		onNamespaceHandles: []EnvHandle{},
		modules:            []ModuleProvider{},
		moduleNameMap:      map[string]*ModuleProvider{},
		datas:              map[string]*DataProvider{},
		models:             sync.Map{},
	}
	return env
}

func (env *Environment) ID() string {
	return env.id
}

func (env *Environment) OpenLink() error {
	return InitLinkFrom(fmt.Sprintf("%s_"+LinkTable, env.id), env.id)
}

func (env *Environment) SetApp(app binocle.App) {
	env.app = app
}

func (env *Environment) Context() def.Context {
	return env.context
}

func (env *Environment) Transaction() (orm.Transaction, error) {
	return orm.DefaultExecutor().Begin()
}

func (env *Environment) SetConfig(key string, value interface{}) error {
	return env.config.Set(key, value)
}

func (env *Environment) Config(key string, value interface{}) error {
	return env.config.Get(key, value)
}

func (env *Environment) GetCache(openId string, key string, value interface{}) error {
	return env.Get(fmt.Sprintf(key+"/"+openId), value)
}

func (env *Environment) SetCache(openId string, key string, value interface{}, expire int) error {
	return env.SetExpire(fmt.Sprintf(key+"/"+openId), value, expire)
}

func (env *Environment) Time() time.Time {
	return env.lastTime
}

func (env *Environment) RegisterModel(name string, record interface{}) (*Table, error) {
	if _, ok := env.models.Load(name); ok {
		return nil, errors.New("[ Env ] Model " + name + " is exist!")
	}
	model := orm.NewModel(fmt.Sprintf("%s_"+name, env.id), record, nil)
	env.models.Store(name, model)
	table := &Table{Model: model}
	AddTableTo(table, env.id)
	return table, nil
}

func (env *Environment) RegisterTable(name string, record interface{}, table *Table) (*Table, error) {
	if _, ok := env.models.Load(name); ok {
		return nil, errors.New("[ Env ] Model " + name + " is exist!")
	}
	model := orm.NewModel(fmt.Sprintf("%s_"+name, env.id), record, nil)
	env.models.Store(name, model)
	table.Model = model
	AddTableTo(table, env.id)
	return table, nil
}

func (env *Environment) GetModel(name string) (*orm.Model, error) {
	if m, ok := env.models.Load(name); ok {
		return m.(*orm.Model), nil
	}
	return nil, errors.New("[ Env ] Model " + name + " is not exist!")
}

func (env *Environment) RegisterInit(handle EnvHandle) {
	env.onInitHandles = append(env.onInitHandles, handle)
}

func (env *Environment) RegisterNamespace(handle EnvHandle) {
	env.onNamespaceHandles = append(env.onNamespaceHandles, handle)
}

func (env *Environment) RegisterData(data *DataProvider) {
	if !env.reload {
		// 重新加载时，不判断存在
		if _, ok := env.datas[data.Name]; ok {
			env.context.Debugf("[ Env ] Data: %s exist, pass", data.Name)
			return
		}
	}
	env.datas[data.Name] = data
}

func (env *Environment) RegisterModule(module ModuleProvider) error {
	if module.Depends != nil && len(module.Depends) > 0 {
		for _, name := range module.Depends {
			if _, ok := env.moduleNameMap[name]; ok {
				env.context.Debugf("[ Env ] Module: %s depend %s exist, pass", module.Name, name)
				continue
			}
			if m, ok := providers[name]; ok {
				env.context.Debugf("[ Env ] Module: %s depend %s exist global, register", module.Name, name)
				if err := env.RegisterModule(m); err != nil {
					return err
				}
				continue
			}
			return errors.New("[ Env ]  Module: " + module.Name + " depend " + name + " not exist")
		}
	}
	env.modules = append(env.modules, module)
	env.moduleNameMap[module.Name] = &env.modules[len(env.modules)-1]
	env.context.Debugf("[ Env ] Module: %s register, %v", module.Name, module.Depends)
	return nil
}

func (env *Environment) BindModule(name string) error {
	if module, ok := providers[name]; ok {
		return env.RegisterModule(module)
	}
	return errors.New("[ Env ] Global Module " + name + "is not exist!")
}

func (env *Environment) UpdateUserStatus(openId string, key string, value string) error {
	var result map[string]interface{}
	_ = env.Get(fmt.Sprintf("status:%s", openId), &result)
	if result == nil {
		result = make(map[string]interface{})
	}
	result[key] = value
	if err := env.Set(fmt.Sprintf("status:%s", openId), result); err != nil {
		return err
	}
	return nil
}

func (env *Environment) RefreshUserStatus(openId string, clean bool) error {
	if clean {
		//if err := cache.Clean(fmt.Sprintf("status:%s", openId)); err != nil {
		//	return err
		//}
		return nil
	}
	if err := env.RefreshExpire(fmt.Sprintf("status:%s", openId)); err != nil {
		return err
	}
	return nil
}

func (env *Environment) GetUserStatus(openId string) map[string]interface{} {
	var result map[string]interface{}
	if err := env.Get(fmt.Sprintf("status:%s", openId), &result); err != nil {
		return map[string]interface{}{}
	}
	return result
}

func (env *Environment) SendEventNotice(openId string, args []interface{}) {
	if env.app != nil {
		env.app.Notice(openId, "event/notice", args...)
	}
}

func (env *Environment) SetNamespace() error {
	env.namespace = true
	for _, module := range env.modules {
		if module.OnNamespace != nil {
			env.context.Debugf("[ Env ] Module: %s namespace", module.Name)
			if err := module.OnInit(env); err != nil {
				return err
			}
		}
	}
	for _, handle := range env.onNamespaceHandles {
		if err := handle(env); err != nil {
			return err
		}
	}
	return nil
}

func (env *Environment) Start() error {
	if env.app != nil {
		env.app.MountClient(func(client *cluster.Client) {
			client.On("reload/game", func(message asteroid.Message) {
				env.reload = true
				for _, module := range env.modules {
					if module.OnReload != nil {
						_ = module.OnReload(env)
					}
					if env.namespace {
						if module.OnNamespace != nil {
							_ = module.OnNamespace(env)
						}
					}
				}
				for _, handle := range env.onInitHandles {
					_ = handle(env)
				}
				if env.namespace {
					for _, handle := range env.onNamespaceHandles {
						_ = handle(env)
					}
				}
				env.lastTime = time.Now()
				env.reload = false
			})
		})
		for _, module := range env.modules {
			if module.OnApp != nil {
				env.context.Debugf("[ Env ] Module: %s App", module.Name)
				if err := module.OnApp(env.app, env); err != nil {
					return err
				}
			}
		}
	}
	for _, module := range env.modules {
		if module.OnInit != nil {
			env.context.Debugf("[ Env ] Module: %s init", module.Name)
			if err := module.OnInit(env); err != nil {
				return err
			}
		}
	}
	for _, handle := range env.onInitHandles {
		if err := handle(env); err != nil {
			return err
		}
	}
	env.lastTime = time.Now()
	return nil
}

func (env *Environment) BindNamespace(namespace nebula.Namespace) {

}

func (env *Environment) BindRouter(r *SwaggerRouter) {
	if env.app == nil {
		return
	}
	router := r.Router
	router.Put("/reload/game", func(context web.Context, next web.Next) error {
		client := env.app.Client()
		client.All().Send("reload/game")
		return next(nil)
	})
	router.Get("/language/list", func(context web.Context, next web.Next) error {
		context.SetResponse(env.LanguageList)
		return nil
	})
	router.Get("/data/version", func(context web.Context, next web.Next) error {
		context.SetResponse(env.lastTime.Unix())
		return nil
	})
	router.Get("/data/list", func(context web.Context, next web.Next) error {
		l := make([]string, 0, len(env.datas))
		for name, _ := range env.datas {
			l = append(l, name)
		}
		context.SetResponse(l)
		return nil
	})
	// 下载后台配置项
	// data下载数据
	// lang下载语言文件
	router.Get("/data/download", func(context web.Context, next web.Next) error {
		buf := &bytes.Buffer{}
		zw := zip.NewWriter(buf)
		manager := false
		m, err := context.GetParam("manager")
		if err == nil {
			manager = utils.ToBool(m)
		}
		data, err := context.GetParamArray("data")
		if err != nil {
			data = nil
		} else {
			sort.Strings(data)
		}

		lang, err := context.GetParamArray("lang")
		if err != nil {
			lang = nil
		} else {
			sort.Strings(lang)
		}
		langStore := NewLanguageStore()
		for _, language := range env.LanguageList {
			if lang != nil {
				i := sort.SearchStrings(lang, language)
				if i >= 0 && i < len(data) && lang[i] != language {
					continue
				}
			}
			langStore.AddLang(language)
		}

		f := func(name string, info interface{}, array bool) error {
			slot := serialize.GetSlot()
			slot.Bind(msgpack.Msgpack(array))
			defer slot.Release()
			w, err := zw.Create(name + ".bytes")
			if err != nil {
				return err
			}
			if err := slot.Set(info); err != nil {
				return err
			}
			_, err = w.Write(slot.Bytes())
			return err
		}
		for name, provider := range env.datas {
			if provider.Manager != manager {
				continue
			}
			if data != nil {
				i := sort.SearchStrings(data, name)
				if i >= 0 && i < len(data) && data[i] != name {
					continue
				}
			}
			info, err := provider.Get()
			if err != nil {
				return err
			}
			if provider.Array {
				if err := f(name, info, provider.Array); err != nil {
					return err
				}
			}
			if !provider.Language || provider.Manager {
				continue
			}
			if reflect.TypeOf(info).Kind() != reflect.Slice {
				continue
			}
			r := reflect.ValueOf(info)
			list := make([]DataProvide, r.Len())
			for i := 0; i < r.Len(); i++ {
				ele := r.Index(i)
				v, ok := ele.Interface().(DataProvide)
				if !ok {
					return err
				}
				list[i] = v
			}
			for _, item := range list {
				langStore.Parse(provider, item.Inline())
			}
		}
		if !manager {
			for _, lang := range langStore.langs {
				languageName := "language/" + lang
				if err := f(languageName, langStore.List[lang], true); err != nil {
					return err
				}
			}
		}
		if err := zw.Close(); err != nil {
			return err
		}
		return web.Download(context, buf.Bytes(), "data.zip", "")
	})

	r.Post("/test/room", Swagger{
		Controller: func(context def.Context, userId string, options map[string]interface{}) (string, error) {
			members := map[string]string{userId: ""}
			token := env.RoomManager.Create(env.app, members, options)
			return token, nil
		},
		Params: []Param{
			{Name: `userId`, In: Body, Required: true},
			{Name: `options`, In: All, Required: true},
		},
		Response: ApiResponse,
	})
}

func (env *Environment) GetRoom(openId string) string {
	status := env.GetUserStatus(openId)
	if v, ok := status[RoomKey]; ok {
		return utils.ToString(v)
	} else {
		return ""
	}
}

func (env *Environment) LeaveRoom(token string, openId string) {
	_ = env.UpdateUserStatus(openId, RoomKey, "")
}

func (env *Environment) JoinTeam(token string, openId string) {
	_ = env.UpdateUserStatus(openId, TeamKey, token)
}

func (env *Environment) GetTeam(openId string) string {
	status := env.GetUserStatus(openId)
	if v, ok := status[TeamKey]; ok {
		return utils.ToString(v)
	} else {
		return ""
	}
}

func (env *Environment) LeaveTeam(token string, openId string) {
	_ = env.UpdateUserStatus(openId, TeamKey, "")
}

func (env *Environment) AllowTeam(token string, openId string) error {
	info := env.GetUserStatus(openId)
	if info == nil {
		return nil
	} else if value, ok := info["team"]; ok && value != "" {
		return errors.New("ALREADY_IN_TEAM")
	} else {
		return nil
	}
}

func GetLanguage(context def.Context) string {
	var language string
	if err := context.Get(LanguageKey, &language); err != nil {
		language = DefaultLanguage
	}
	return language
}

func GetZone(context def.Context) *time.Location {
	var zone *time.Location
	if err := context.Get(ZoneKey, &zone); err != nil {
		zone, _ = time.LoadLocation("Local")
	}
	return zone
}

func GetUpdateTime(context def.Context, updateTime int) *time.Time {
	if updateTime > 0 {
		t := time.Unix(int64(updateTime), 0)
		return &t
	} else {
		return nil
	}
}
